package drds.configuration.db.xml_mapping.sql_execute;


import drds.configuration.db.xml_mapping.MapperFactory;
import drds.configuration.db.xml_mapping.annotation.dto.Dto;
import drds.configuration.db.xml_mapping.annotation.dto.DtoMapping;
import drds.configuration.db.xml_mapping.annotation.mapper_class.BatchInsert;
import drds.configuration.db.xml_mapping.annotation.mapper_class.Key;
import drds.configuration.db.xml_mapping.configuration.Configuration;
import drds.configuration.db.xml_mapping.configuration.ResultMap;
import drds.tools.$;
import drds.tools.ShouldNeverHappenException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class MapperMethod {
    private final MapperMethodSignature mapperMethodSignature;

    public MapperMethod(Configuration configuration, Class<?> mapperInterface, Method method) {
        this.mapperMethodSignature = new MapperMethodSignature(configuration, mapperInterface, method);
    }

    /**
     * 需要进行参数解析为map,要求map不能重名
     */
    public Object execute(MapperExecutionContext mapperExecutionContext, Method method, Object[] args) {
        String mapperId = mapperMethodSignature.getMapperId();
        Mapper mapper = mapperExecutionContext.getConfiguration().getMapper(mapperId);
        Object rowsCount;
        switch (mapperMethodSignature.getSqlType()) {
            case insert: {
                Map<String, Object> parameterMap = MethodArgsToNamedMap.convertArgsToNamedParameterMap(method, args, mapper);
                //
                BatchInsert batchInsert = mapperMethodSignature.getMethod().getAnnotation(BatchInsert.class);
                if (batchInsert == null) {
                    rowsCount = rowsCount(mapperExecutionContext.insert(mapperMethodSignature.getMapperId(), parameterMap));
                    bindValue(mapper, method, args, parameterMap);
                    //进行参数回填
                    return rowsCount;
                } else {
                    List<Map<String, Object>> parameterMapList = getList(method, parameterMap);
                    rowsCount = rowsCount(mapperExecutionContext.batchInsert(mapperMethodSignature.getMapperId(), parameterMapList));
                    bindValues(mapper, method, args, parameterMap);
                    //进行参数回填
                    return rowsCount;
                }
            }
            case update: {
                Map<String, Object> parameterMap = MethodArgsToNamedMap.convertArgsToNamedParameterMap(method, args, mapper);
                rowsCount = rowsCount(mapperExecutionContext.update(mapperMethodSignature.getMapperId(), parameterMap));
                return rowsCount;
            }
            case delete: {
                Map<String, Object> parameterMap = MethodArgsToNamedMap.convertArgsToNamedParameterMap(method, args, mapper);
                rowsCount = rowsCount(mapperExecutionContext.delete(mapperMethodSignature.getMapperId(), parameterMap));
                return rowsCount;
            }
            case select: {
                if (mapperMethodSignature.isReturnsVoid()) {
                    executeWithReturnsVoid(mapperExecutionContext, method, args, mapper);
                    rowsCount = null;
                    return rowsCount;
                } else if (mapperMethodSignature.isReturnsList()) {
                    if (mapperMethodSignature.isReturnsMap()) {
                        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
                        return mapList;
                    } else if (mapperMethodSignature.isReturnsDto()) {
                        List<Object> objectList = selectDtoList(mapperExecutionContext, method, args, mapper);
                        return objectList;
                    } else if (mapperMethodSignature.isReturnsBasicType()) {
                        List list = selectBasicTypeList(mapperExecutionContext, method, args, mapper);
                        return list;
                    } else {
                        throw new ShouldNeverHappenException();
                    }
                } else {
                    if (mapperMethodSignature.isReturnsMap()) {
                        rowsCount = selectMap(mapperExecutionContext, method, args, mapper);
                        return rowsCount;
                    } else if (mapperMethodSignature.isReturnsDto()) {
                        rowsCount = selectDto(mapperExecutionContext, method, args, mapper);
                        return rowsCount;
                    } else if (mapperMethodSignature.isReturnsBasicType()) {
                        rowsCount = selectBasicType(mapperExecutionContext, method, args, mapper);
                        return rowsCount;
                    } else {
                        throw new ShouldNeverHappenException();
                    }
                }
            }
            default:
                throw new IllegalArgumentException();
        }

    }

    private Object rowsCount(int rowCount) {
        final Object result;
        if (mapperMethodSignature.isReturnsVoid()) {
            result = null;
        } else if (Integer.class.equals(mapperMethodSignature.getReturnType()) || Integer.TYPE.equals(mapperMethodSignature.getReturnType())) {
            result = rowCount;
        } else if (Long.class.equals(mapperMethodSignature.getReturnType()) || Long.TYPE.equals(mapperMethodSignature.getReturnType())) {
            result = (long) rowCount;
        } else if (Boolean.class.equals(mapperMethodSignature.getReturnType()) || Boolean.TYPE.equals(mapperMethodSignature.getReturnType())) {
            result = rowCount > 0;
        } else {
            throw new RuntimeException("MapperFactory mapperMethodSignature '" + mapperMethodSignature.getMapperId() + "' has an unsupported return parameter_and_result_value_type: " + mapperMethodSignature.getReturnType());
        }
        return result;
    }

    private void bindValue(Mapper mapper, Method method, Object[] args, Map<String, Object> parameterMap) {
        Annotation[][] annotationss = method.getParameterAnnotations();
        for (int i = 0; i < annotationss.length; i++) {
            Annotation[] annotations = annotationss[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof Key) {
                    Key key = (Key) annotation;
                    String value = key.key();
                    if (value.equals(mapper.getPrimaryKeyAlias())) {
                        //map->dto
                        Object object = args[i];
                        if (object != null) {
                            if (object instanceof ArrayList) {
                                ArrayList objectList = (ArrayList) object;
                                if ($.isNotNullAndHasElement(objectList) && objectList.get(0).getClass().getAnnotation(Dto.class) != null) {
                                    DtoMapping dtoMapping = MapperFactory.DtoClassMapping.classToDtoMappingMap.get(objectList.get(0).getClass());
                                    Field field = dtoMapping.getColumnNameToFieldMap().get(mapper.getPrimaryKeyColumnName());
                                    //
                                    List<Map<String, Object>> mapList = (List<Map<String, Object>>) parameterMap.get(mapper.getPrimaryKeyAlias());
                                    int index = 0;
                                    for (Map<String, Object> map : mapList) {
                                        long primaryKeyColumnValue = (Long) map.get(mapper.getPrimaryKeyColumnName());
                                        try {
                                            field.set(objectList.get(index), primaryKeyColumnValue);
                                        } catch (IllegalAccessException e) {
                                            e.printStackTrace();
                                        }
                                        index++;
                                    }
                                }//end dto
                            } else {
                                if (object != null && object.getClass().getAnnotation(Dto.class) != null) {
                                    DtoMapping dtoMapping = MapperFactory.DtoClassMapping.classToDtoMappingMap.get(object.getClass());
                                    Field field = dtoMapping.getColumnNameToFieldMap().get(mapper.getPrimaryKeyColumnName());
                                    Map<String, Object> map = (Map<String, Object>) parameterMap.get(mapper.getPrimaryKeyAlias());
                                    long primaryKeyColumnValue = (Long) map.get(mapper.getPrimaryKeyColumnName());
                                    try {
                                        field.set(object, primaryKeyColumnValue);
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    }
                                }//end dto
                            }
                        }

                    }

                }
            }
        }

    }

    /**
     * 用于批量insert(with bindValues)
     */
    private List<Map<String, Object>> getList(Method method, Map<String, Object> parameterMap) {
        List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
        Annotation[] annotations = method.getParameterAnnotations()[0];
        for (Annotation annotation : annotations) {
            if (annotation instanceof Key) {
                Key key = (Key) annotation;
                String value = key.key();
                mapList = (List<Map<String, Object>>) parameterMap.get(value);
                return mapList;
            }
        }
        throw new ShouldNeverHappenException();
    }

    private void bindValues(Mapper mapper, Method method, Object[] args, Map<String, Object> parameterMap) {
        Annotation[][] annotationss = method.getParameterAnnotations();
        for (int i = 0; i < annotationss.length; i++) {
            Annotation[] annotations = annotationss[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof Key) {
                    Key key = (Key) annotation;
                    String value = key.key();
                    if (value.equals(mapper.getPrimaryKeyAlias())) {
                        //map->dto
                        Object object = args[i];
                        if (object != null) {
                            if (object instanceof ArrayList) {
                                ArrayList objectList = (ArrayList) object;
                                if ($.isNotNullAndHasElement(objectList) && objectList.get(0).getClass().getAnnotation(Dto.class) != null) {
                                    DtoMapping dtoMapping = MapperFactory.DtoClassMapping.classToDtoMappingMap.get(objectList.get(0).getClass());
                                    Field field = dtoMapping.getColumnNameToFieldMap().get(mapper.getPrimaryKeyColumnName());
                                    //
                                    List<Map<String, Object>> mapList = (List<Map<String, Object>>) parameterMap.get(value);
                                    int index = 0;
                                    for (Map<String, Object> map : mapList) {
                                        long primaryKeyColumnValue = (Long) map.get(mapper.getPrimaryKeyColumnName());
                                        try {
                                            field.set(objectList.get(index), primaryKeyColumnValue);
                                        } catch (IllegalAccessException e) {
                                            e.printStackTrace();
                                        }
                                        index++;
                                    }
                                    //end dto
                                } else if ($.isNotNullAndHasElement(objectList) && (objectList.get(0) instanceof Map)) {

                                } else {
                                    throw new IllegalArgumentException("只支持list<map>和list<dto>");
                                }
                            } else {
                                throw new ShouldNeverHappenException();
                            }
                        }

                    }

                }
            }
        }

    }


    //
    public List<Map<String, Object>> selectMapList(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        Map<String, Object> parameterMap = MethodArgsToNamedMap.convertArgsToNamedParameterMap(method, args, mapper);
        List<Map<String, Object>> mapList = mapperExecutionContext.selectMapList(mapperMethodSignature.getMapperId(), parameterMap);
        return mapList;
    }

    private void executeWithReturnsVoid(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        selectMapList(mapperExecutionContext, method, args, mapper);
    }

    //
    private List<Object> selectDtoList(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
        Class dtoClass = mapperMethodSignature.getDtoClass();
        //构建dto
        //需要得到映射关系,优先选择dtoMap,然后才是dto对应的映射关系
        ResultMap resultMap = mapper.getResultMap();
        if (resultMap.getResultMapClass() == null) {
            throw new IllegalArgumentException("请先在mapper.xml配置返回参数dto类型");
        }
        if (dtoClass != resultMap.getResultMapClass()) {
            throw new IllegalArgumentException("mappe类型r.xml中配置返回参数dto类型和方法具体调用的dto类型不一致");
        }
        List<Object> objectList = new ArrayList<Object>();
        for (Map<String, Object> map : mapList) {
            Object object = resultMap.bind(map);
            objectList.add(object);
        }
        return objectList;
    }

    private List selectBasicTypeList(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
        if (mapList == null) {
            return null;
        } else {
            if (mapList.size() == 0) {
                return null;
            } else {
                List<Object> objectList = new ArrayList<Object>();
                for (Map<String, Object> map : mapList) {
                    Set<Map.Entry<String, Object>> set = map.entrySet();
                    if (set.size() > 1) {
                        throw new IllegalArgumentException("返回多条数据");
                    }
                    Object object = null;
                    for (Map.Entry<String, Object> entry : set) {
                        object = entry.getValue();//代码里面来保证只有一个值
                        break;
                    }
                    objectList.add(object);
                }
                return objectList;
            }
        }
    }
    //

    private Map<String, Object> selectMap(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
        if (mapList == null) {
            return null;
        } else {
            if (mapList.size() == 0) {
                return null;
            } else if (mapList.size() == 1) {
                return mapList.get(0);
            } else {
                throw new IllegalArgumentException("返回多条数据");
            }
        }
    }

    private Object selectDto(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
        if (mapList == null) {
            return null;
        } else {
            if (mapList.size() == 0) {
                return null;
            } else if (mapList.size() == 1) {
                Class dtoClass = mapperMethodSignature.getDtoClass();
                //构建dto
                //需要得到映射关系,优先选择dtoMap,然后才是dto对应的映射关系
                ResultMap resultMap = mapper.getResultMap();
                if (resultMap.getResultMapClass() == null) {
                    throw new IllegalArgumentException("请先在mapper.xml配置返回参数dto类型");
                }
                if (dtoClass != resultMap.getResultMapClass()) {
                    throw new IllegalArgumentException("mapper.xml中配置返回参数dto类型和方法具体调用的dto类型不一致");
                }
                Map<String, Object> map = mapList.get(0);
                Object object = resultMap.bind(map);
                return object;
            } else {
                throw new IllegalArgumentException("返回多条数据");
            }
        }
    }

    private Object selectBasicType(MapperExecutionContext mapperExecutionContext, Method method, Object[] args, Mapper mapper) {
        List<Map<String, Object>> mapList = selectMapList(mapperExecutionContext, method, args, mapper);
        if (mapList == null) {
            return null;
        } else {
            if (mapList.size() == 0) {
                return null;
            } else if (mapList.size() == 1) {
                Map<String, Object> map = mapList.get(0);
                Set<Map.Entry<String, Object>> set = map.entrySet();
                if (set.size() > 1) {
                    throw new IllegalArgumentException("返回多条数据");
                }
                Object object = null;
                for (Map.Entry<String, Object> entry : set) {
                    object = entry.getValue();//代码里面来保证只有一个值
                    break;
                }
                return object;
            } else {
                throw new IllegalArgumentException("返回多条数据");
            }
        }
    }

}
